<?php
require_once "Logger.class.php";
require_once "HttpClient.class.php";
require_once "CookieEncoder.class.php";
require_once "CookieDecoder.class.php";
require_once "CommandParser.class.php";

// 使用socket方式http
define("RVSPOPT_SOCKETHTTP",10);
// 强制转换编码为utf-8
define("RVSPOPT_TEXT_FORCE_CHARSET",20);
// 移除script标签
define("RVSPOPT_HTML_REMOVE_SCRIPT",30);
// 移除样式
define("RVSPOPT_HTML_REMOVE_STYLE",40);
// 移除图片
define("RVSPOPT_HTML_REMOVE_IMAGE",50);
// 移除flash
define("RVSPOPT_HTML_REMOVE_FLASH",60);
// 移除内嵌的iframe
define("RVSPOPT_HTML_REMOVE_FRAME",70);
// 转换HTML实体标记为正常字符
define("RVSPOPT_HTML_DECODE_HTMLENTITY",80);
// 重写超链接路径
// 替换<Meta>,<Header> Location, <A><link> Href，<form> Action<iframe>中的超链接地址
define("RVSPOPT_HTML_REWRITE_HYPERLINK",90);
// 重写资源文件路径
// 代理资源文件，较耗流量，务必慎用，用户访问此网站的所有流量将转嫁到你的网站
// 将替换css文件中的background:url，<image><style><style>标签中的src中的属性
define("RVSPOPT_HTML_REWRITE_RESOURCEPATH",100);
// 待移除的域名中的特殊字符，反gfw域名关键字屏蔽
define("RVSPOPT_PENDING_REMOVE_HOSTNAME_SPECIALCHARS",110);
// 自定义USERAGENT
define("RVSPOPT_USERAGENT",120);
/**
 * ReversedProxy
 * 反向代理
 */
class ReversedProxy {

	// HTTP 请求构造
	private $_request_url;
	private $_request_params;
	private $_request_method;
	private $_request_multi;

	// HTTP client instance
	private $_client;
	// 代理路径
	private $_url_prefix;

	// 强制使用socket http
	private $_use_socket_http = false;
	// 强制编码
	private $_charset = false;
	// 移除Javascript
	private $_no_script = false;
	// 移除样式表
	private $_no_style = false;
	// 移除图片
	private $_no_image = false;
	// 移除Flash
	private $_no_flash = false;
	// 移除内嵌的iframe
	private $_no_embed_frame = false;
	// 转换html实体
	private $_decode_html_entity = false;
	// 转换后的超链接前缀
	private $_rewrite_hyperlink = false;
	// 资源路径前缀
	private $_rewrite_resource = false;
	// 待移除的域名中的特殊字符（防止GFW屏蔽）
	private $_pending_remove_hostname_specialchars;
	// UA
	private $_user_agent;

	//
	private $_commands;

	public function __construct($url, $params=array(), $method="GET", $multi=false) {
		$this->_request_url = $url;
		$this->_request_params = $params;
		$this->_request_method = $method;
		$this->_request_multi = $multi;
		$this->_url_prefix = "http://".SERVER_HOST."/".MAGIC_STR.URL_BASE_PATH;
		$this->_commands = new CommandParser(URL_BASE_PATH);
	}

	public function setopt($opt_name,$opt_value) {
		switch ($opt_name) {
		    case RVSPOPT_SOCKETHTTP:
		    $this->_use_socket_http = (boolean)$opt_value;
			break;
		    case RVSPOPT_TEXT_FORCE_CHARSET:
		    $this->_charset = trim(strtolower($opt_value));
			break;
		    case RVSPOPT_HTML_REMOVE_SCRIPT:
		    $this->_no_script = (boolean)$opt_value;
			break;
		    case RVSPOPT_HTML_REMOVE_STYLE:
		    $this->_no_style = (boolean)$opt_value;
			break;
		    case RVSPOPT_HTML_REMOVE_IMAGE:
		    $this->_no_image = (boolean)$opt_value;
			break;
		    case RVSPOPT_HTML_REMOVE_FLASH:
		    $this->_no_flash = (boolean)$opt_value;
			break;
		    case RVSPOPT_HTML_REMOVE_FRAME:
		    $this->_no_embed_frame = (boolean)$opt_value;
			break;
		    case RVSPOPT_HTML_DECODE_HTMLENTITY:
		    $this->_decode_html_entity = (boolean)$opt_value;
			break;
		    case RVSPOPT_HTML_REWRITE_HYPERLINK:
		    $this->_rewrite_hyperlink = (boolean)$opt_value;
			break;
		    case RVSPOPT_HTML_REWRITE_RESOURCEPATH:
		    $this->_rewrite_resource = (boolean)$opt_value;
			break;
		    case RVSPOPT_PENDING_REMOVE_HOSTNAME_SPECIALCHARS:
		    $this->_pending_remove_hostname_specialchars = $opt_value;
			break;
		    case RVSPOPT_USERAGENT:
		    $this->_user_agent = $opt_value;
			break;
		}
	}

	// 为URI添加完整的代理路径
	private function enableProxyForURI($uri) {
		$url_parts = $this->_client->getParsedUrl();
		if (preg_match("/^(javascript)/",$uri) || preg_match("/^#/",$uri)) {
			return $uri;
		}else if (preg_match("@^http@si", $uri)) { // HTTP绝对路径
			return $this->_url_prefix.$uri;
		} else if(preg_match("@^\/@si", $uri)) { // 访问站的绝对路径
			$root_url = $url_parts["scheme"]."://".$url_parts["host"];
			return $this->_url_prefix.$root_url.$uri;
		} else if(preg_match("@^\.@si", $uri)) { // 相对路径 ./ 或 ../
	    	$base_path = pathinfo($url_parts["path"]);
	    	$base_dir_name = $base_path["dirname"];
			$ret = $this->relativeToAbsolute($base_dir_name,$uri);
            $root_url = $url_parts["scheme"]."://".$url_parts["host"];
            return $this->_url_prefix.$root_url.$ret;
		} else if(preg_match("@^\w@si", $uri)) {
			$url_path = pathinfo($url_parts["path"]);
			if ($url_path["dirname"] == "\\") {
                $url_path["dirname"] = "";
			}
			$relative_url = $url_parts["scheme"]."://".$url_parts["host"].$url_path["dirname"]."/";
			return $this->_url_prefix.$relative_url.$uri;
		}
	}

	/* 相对路径转换为绝对路径
  	 * 如 urlbase 为 http://www.xxx.com/a/b/c
  	 * $relative_path 为 "../images/a.gif"
  	 * 则输出 http://www.xxx.com/a/b/images/a.gif
	 */
	private function relativeToAbsolute($uri_base,$relative_path) {
		$base_dir_name_parts = explode("/",$uri_base); 

		$path_parts = explode("/",$relative_path);
		$i = count($path_parts);
		$path_dummy = array();
		while($i > 0) {
			$path_part = $path_parts[$i-1];
			if ($path_part == "..") { // 回退一级目录
				unset($base_dir_name_parts[count($base_dir_name_parts) - 1]);
			} else if ($path_part == ".") {
			} else {
				$path_dummy[] = $path_part;
			}
			$i--;
		}
        $path_dummy = array_reverse($path_dummy);
		$ret = array_merge($base_dir_name_parts,$path_dummy);
		return implode("/",$ret);
	}

	// 重写访问路径
	private function rewriteURL($matches) {
		return $matches[1].$this->enableProxyForURI($matches[2]);
	}

	private function rewriteCSSBackground($matches) {
		// base64不做任何改变
		if (preg_match("@\s*data\s*:\*image@",$matches[2])) {
			return $matches[1].$matches[2];
		} else {
			return $this->rewriteURL($matches);
		}
	}

	/**
	 * 代理请求
	 */
	public function exec_proxy($before_response_body_send=NULL,$before_response_header_send=NULL) {

		if ($this->_commands->has("res")) {
			$this->setopt(RVSPOPT_HTML_REWRITE_RESOURCEPATH,true);
		}

		if ($this->_commands->has("ua")) {
			$myua = $this->_commands->getParams("ua");
			if (!array_key_exists("0",$myua)) {
                $myua = "unknown";
			}
			$this->setopt(RVSPOPT_USERAGENT,$myua[0]);
		}

		if ($this->_commands->has("rps")) {
			$myrs = $this->_commands->getParams("rps");
			if ($myrs && !array_key_exists("0",$myrs)) {
                $myrs = "";
			}
			$this->setopt(RVSPOPT_PENDING_REMOVE_HOSTNAME_SPECIALCHARS,$myrs[0]);
		}

		// 根据命令设置参数

		if(!empty($this->_pending_remove_hostname_specialchars)) {
			// 去除指定的特殊字符，如!
		    $request_url_parts = parse_url($this->_request_url);
			$request_url_parts["host"] = str_replace($this->_pending_remove_hostname_specialchars,"",$request_url_parts["host"]);
			// 还原url
			$this->_request_url = preg_replace("@http(s)?:\/\/[^\/]*@si","http\$1://".$request_url_parts["host"],$this->_request_url);
		}

    	$client = new HttpClient($this->_request_url,$this->_request_params,$this->_request_method,$this->_request_multi);
		$this->_client = $client;
		$client->setUseSoketHttp($this->_use_socket_http);

		if (empty($this->_user_agent)) {
    	    $client->setUserAgent($_SERVER["HTTP_USER_AGENT"]);// 未指定UA则userAgent直接透传
		} else {
    	    $client->setUserAgent($this->_user_agent);// 指定UA则使用指定的userAgent
		}
		// 透传Cookie
		$cookie_decoder = new CookieDecoder($this->_request_url);
		$cookie_str = $cookie_decoder->getCookie();
		if (!empty($cookie_str)) {
		    $client->addRequestHeader($cookie_str);
		}

		// 发送代理请求
    	$client->sendRequest();

		// 从远程服务器收到的结果
		$response["http_code"] = $client->getResponseHttpCode();
		$response["header"] = $client->getResponseHeader($as_array=true);
		$response["body"] = $client->getResponseBody();
		$response["encoding"] = $client->detectEncoding();
		$response["mime"] = $client->getResponseContentType();
		$response["is_text"] = preg_match("@text@si",$response["mime"]);
		$response["is_html"] = preg_match("@html@si",$response["mime"]);
		$response["is_css"] = preg_match("@css@si",$response["mime"]);
		$response["is_js"] = preg_match("@javascript@si",$response["mime"]);
		$response["is_image"] = preg_match("@image@si",$response["mime"]);

		// 强制编码转换
		if ( $response["is_text"] && $this->_charset) {
			if (empty($response["encoding"])) {
				Logger::warn("放弃转换编码至<strong>".$this->_charset."</strong>，无法确定源编码。");
			} else if($this->_charset == $response["encoding"]) {
				Logger::warn("放弃转换编码至<strong>".$this->_charset."</strong>，源编码与目标编码一致。");
			} else if(!function_exists("iconv")){
				Logger::warn("放弃转换编码至<strong>".$this->_charset."</strong>，缺少编码转换支持库iconv。");
			} else {
                $response["body"] = iconv($response["encoding"], $this->_charset, $response["body"]);
		    	// 更改HTTP头中编码声明
		    	if (array_key_exists("Content-Type",$response["header"])) {
		    		$header_content_type = $response["header"]["Content-Type"];
					if (preg_match("@charset@",$header_content_type)) { // 有charset
		    		    $response["header"]["Content-Type"] = preg_replace("/charset\s*=\s*\w+/i","charset=".$this->_charset,$header_content_type);
					} else { // 无charset
				    	$header_content_type = preg_replace("@;*$@","",$header_content_type);// 移除末尾的分号，避免出现多重分号
				    	$header_content_type .= ";charset=".$this->_charset;
                        $response["header"]["Content-Type"] = $header_content_type;// 写入转换后的charset
					}
		    	}

		    	// 更改HTTP包体中编码声明
				// HTML <meta charset="xxx">
				// HTML @charset "utf-8";
		    	if (preg_match("@charset\s*=?\s*[\"\']?\s*([^\"^']*)@si",$response["body"])) {
                    $response["body"] = preg_replace("@(charset\s*=?\s*[\"\']?\s*)([^\"^']*)@si","\$1".$this->_charset,$response["body"]);
		    	}
			}
		}

		if ($response["is_html"]) {
	    	// 移除javascript,包括内嵌的JS和外部引用的JS
	    	if( $this->_no_script){
	    		$response["body"] = preg_replace("@<script[^>]*?>.*?</script>@si","\n",$response["body"]);
	    	}
	    	// 移除style,包括内嵌的style和外部引用的style
	    	if( $this->_no_style){
	    		$response["body"] = preg_replace(array("@<link[^>]*?/>@si","@<style[^>]*?>.*?</style>@si"),"\n",$response["body"]);
	    	}
	    	// 移除image标签
	    	if( $this->_no_image){
	    		$response["body"] = preg_replace(array("@<img[^>]*?>@si","@</img>@si"),"\n",$response["body"]);
	    	}
	    	// 移除flash标签
	    	if( $this->_no_flash){
	    		$response["body"] = preg_replace(array("@<embed[^>]*?>@si","@</embed>@si"),"\n",$response["body"]);
	    	}
	    	// 移除iframe标签
	    	if( $this->_no_embed_frame){
	    		$response["body"] = preg_replace(array("@<iframe[^>]*?>@si","@</iframe>@si"),"\n",$response["body"]);
	    	}

			if ($this->_rewrite_hyperlink) {
				// <meta><form><a><iframe>
				$response["body"] = preg_replace_callback(array("@(http\-equiv\s*=\s*[^>]*\d+;+\s*URL\s*=\s*)([^>^\"^']*)@si",
					                                            "@(<form[^>]*action\s*=\s*[\"\'])([^>^\"^']*)@si",
					                                            "@(<a[^>]*href\s*=\s*[\"\'])([^>^\"^']*)@si",
																"@(<iframe[^>]*src\s*=\s*[\"\'])([^>^\"^']*)@si"
				                                               ),
				                                          array(&$this,"rewriteURL"),
				                                          $response["body"]);
			}

	    	if($this->_rewrite_resource) {
				//  内嵌在HTML页面中的css代码
				$response["body"] = preg_replace_callback("@(url\([\"']?)([^\)^\"^']*)@si",
				                                           array(&$this,"rewriteCSSBackground"),
				                                           $response["body"]);
		    	// <link><image><script>
				$response["body"] = preg_replace_callback(array("@(<link[^>]*href\s*=\s*[\"\'])([^>^\"^']*)@si",
					                                            "@(<img[^>]*src\s*=\s*[\"\'])([^>^\"^']*)@si",
					                                            "@(<script[^>]*src\s*=\s*[\"\'])([^>^\"^']*)@si"
				                                               ),
				                                      	  array(&$this,"rewriteURL"),
				                                          $response["body"]);
	    	}

	    	if($this->_decode_html_entity) {
	    		if ($this->_charset) {
	    		    $response["body"] = html_entity_decode($response["body"],ENT_COMPAT,$this->_charset);
	    		} else if($response["encoding"]) {
	    		    $response["body"] = html_entity_decode($response["body"],ENT_COMPAT,$response["encoding"]);
	    		} else {
	    		    $response["body"] = html_entity_decode($response["body"],ENT_COMPAT,"UTF-8");
	    		}
	    	}

		}

		if ($response["is_css"] && $this->_rewrite_resource) {
	    	//替换css中的相对路径
			$response["body"] = preg_replace_callback("@(url\([\"']?)([^\)^\"^']*)@si"
			                                          ,array(&$this,"rewriteCSSBackground")
			                                          ,$response["body"]);
		}

		if ($before_response_header_send) {
			$response["header"] = call_user_func($before_response_header_send,$response["header"]);
		}

		// 转发HTTP头
		// 不转发的header
		// Transfer-Encoding会引起兼容性问题
		// Location,Set-Cookie有专门的处理逻辑
		$black_header_keynames = array("Transfer-Encoding","Set-Cookie","Location");
		foreach($response["header"] as $header_key => $header_val) {
			    // 非需特殊处理的header，原样转发
	    	if (!in_array($header_key,$black_header_keynames)) {
				// Content-type中无chaset 但是我们检测到了正确的编码则修改包头把charset加上
				// 否则Apache会自动会加上charset="utf-8";如果实际编码并非UTF-8会引起乱码问题
				if($header_key == "Content-Type" && !preg_match("@charset@si",$header_val) && !empty($response["encoding"])) {
					$header_val = preg_replace("@;*$@","",$header_val);// 移除末尾的分号，避免出现多重分号
					$header_val .= ";charset=".$response["encoding"];
				}
	        	if($header_key == "Head") { // "GET / HTTP 1.1"
	        	    $header_content = $header_val;
	        	} else {
	        	    $header_content = $header_key.': '.$header_val;
	        	}
	        	Logger::debug("transfer header ".$header_content);
                header($header_content);
	    	}
		}

		// 转发Location
        if (array_key_exists("Location", $response["header"])) {
			$new_location = "";
		    if($this->_rewrite_hyperlink) {
				$new_location .= $this->_url_prefix;
			}
			$new_location .= $response["header"]["Location"];
			$header_location = 'Location: '.$new_location;
		    Logger::debug("transfer header ".$header_location);
        	header($header_location);
        }

		// 透传cookie操作
		if (array_key_exists("Set-Cookie", $response["header"])) {
			$cookie_encoder = new CookieEncoder($response["header"]["Set-Cookie"],$this->_request_url);
			$encoded_cookies = $cookie_encoder->getEncodedCookie();

	    	foreach($encoded_cookies as $encoded_cookie) {
				$encoded_cookie_header = "Set-Cookie: ".$encoded_cookie;
		        Logger::debug("transfer header ".$encoded_cookie_header);
	    		header($encoded_cookie_header, false);
	    	}
		}

		// 允许外部函数最后输出内容进行修改
		if ($before_response_body_send) {
			$response["body"] = call_user_func($before_response_body_send,$response["body"]);
		}

		Logger::debug("transfer response ".$response["body"]);

		// 转发响应
		echo $response["body"];
	}
}
